//  Brett Jones
//  PBR Senior Thesis
//  Copyright Brett Jones October 2007

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using Exocortex.Geometry3D;
using System.Windows.Forms;
using System.IO;

namespace PBR
{
    /// <summary>
    /// Renders a scene element
    /// </summary>
    public class Renderer
    {
        #region Static Variables

        private static string kBasePath;
        private static string kSavePath;
        private const int DISTANCE = 10;
        private const float LINEWIDTH = 30.0f;
        private const float LINELENGTH = 30.0f;
        private const float OUTERCIRCLERADIUS = 4.0f;
        private const float INNERCIRCLERADIUS = 2.0f;
        private const int NUMITER = 30000;
        private const int NUMSTRONG = 50;
        private const float VIEWANGLE = 60.0f;

        #endregion

        #region Member Variables

        private Scene scene;
        private Render.Painter painter;
        private Vector3 mEyePos;
        private int distance = DISTANCE;
        private float lineWidth = LINEWIDTH;
        private float lineLength = LINELENGTH;
        private float outerCircleRadius = OUTERCIRCLERADIUS;
        private float innerCircleRadius = INNERCIRCLERADIUS;
        private int numIter = NUMITER;
        private int numStrong = NUMSTRONG;
        private float viewAngle = VIEWANGLE;

        #endregion

        #region Member Functions

        //Todo Fix
        public Renderer(Scene scene)
        {
            this.scene = scene;
            this.painter = new PBR.Render.Painter(scene);
            float yDist = (float) ( 1.0 / Math.Tan( ConvertToRadians( viewAngle / 2.0 ) ) );
            mEyePos = Vector3.FromXYZ(0.0f, yDist, 0.0f);
        }

        //public void Composite()
        //{
        //    Bitmap baseImage = scene.Painting.Layers[0].ColorImage.Bitmap;
        //    double angle = 60.0;

        //    AForge.Imaging.Filters.RotateBilinear rotFilter =
        //            new AForge.Imaging.Filters.RotateBilinear(angle);

        //    baseImage = rotFilter.Apply(baseImage);
        //    scene.Painting.OutputImage.Bitmap = baseImage;
        //    baseImage.Save(Path.Combine(scene.BasePath, "rotate.png"));
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="currScene"></param>
        ///// <param name="outputImage"></param>
        ///// <param name="vectorImage"></param>
        //public void GenerateSobel()
        //{
        //    PBR.Filters.SobelEdgeDetectorRotation sobelRot = new PBR.Filters.SobelEdgeDetectorRotation();

        //    Bitmap[] outputImages = new Bitmap[2];
        //    outputImages = sobelRot.Apply(scene.Painting.ActiveLayer.Color.Bitmap);
        //    outputImages[0].Save(Path.Combine(scene.BasePath, "edges" + scene.Painting.ActiveLayerIndex + " .png"));
        //    outputImages[1].Save(Path.Combine(scene.BasePath, "angles" + scene.Painting.ActiveLayerIndex + ".png"));
        //    scene.Painting.ActiveLayer.EdgesImage = new BitmapUnsafe(XMLSchema.EdgesImage, outputImages[0]);
        //    //currScene.Painting.ActiveLayer.AnglesImage = new BitmapUnsafe(XMLSchema.AnglesImage, outputImages[1]);
        //}

        //public void GeneratePaint(Rectangle rect, Bitmap dstBmp)
        //{
        //    if (rect.Size != dstBmp.Size)
        //    {
        //        throw new ArgumentException("srcRect and dstBmp must be of same size");
        //    }

        //    Graphics g = Graphics.FromImage(dstBmp);
        //    BitmapUnsafe anglesBmp = scene.Painting.ActiveLayer.AnglesImage;
        //    Bitmap vectorBmp = scene.Painting.ActiveLayer.VectorImage.Bitmap;

        //    anglesBmp.Lock(ImageLockMode.ReadWrite);

        //    Pen pen = new Pen(new SolidBrush(Color.Black), lineWidth);

        //    g.FillRectangle(new SolidBrush(Color.White), 0, 0, g.VisibleClipBounds.Width, g.VisibleClipBounds.Height);
        //    float x = 0.0f;
        //    float y = 0.0f;
        //    float halfInnerCircleRadius = innerCircleRadius / 2.0f;
        //    float halfOuterCircleRadius = outerCircleRadius / 2.0f;

        //    double angle;

        //    for (int col = rect.Left; col < rect.Right; col += distance)
        //    {
        //        for (int row = rect.Top; row < rect.Bottom; row += distance)
        //        {
        //            angle = ((double)anglesBmp.GetCellBW(row, col))/255.0*Math.PI*2;

        //            x = (float)Math.Cos(angle)*lineLength;
        //            y = (float)Math.Sin(angle)*lineLength;

        //            g.DrawLine(pen, row - x, col - y, row + x, col + y);
        //        }
        //    }

        //    anglesBmp.Unlock();
        //    g.Flush();

        //    //vectorBmp = new Bitmap(anglesBmp.Width, anglesBmp.Height, g);
        //}


        /// <summary>
        /// 
        /// </summary>
        /// <param name="currScene"></param>
        //public unsafe void FluxCapacitor()
        //{
        //    //double gaussianBlurWidth = 15.0;

        //    //Graphics gColor = Graphics.FromImage(scene.Painting.ActiveLayer.PaintImage.Bitmap);
        //    //Graphics gHeight = Graphics.FromImage(scene.Painting.ActiveLayer.HeightResultImage.Bitmap);
        //    //BitmapUnsafe anglesImage = scene.Painting.ActiveLayer.AnglesImage;
        //    //int height = anglesImage.Height;
        //    //int width = anglesImage.Width;
        //    //BitmapUnsafe colorImage = new BitmapUnsafe(XMLSchema.ColorImage, scene.Painting.ActiveLayer.ColorImage);
        //    //BitmapUnsafe edgesImage = scene.Painting.ActiveLayer.EdgesImage;
        //    ////AForge.Imaging.Filters.GaussianBlur gaussianFilter = new AForge.Imaging.Filters.GaussianBlur(gaussianBlurWidth);
        //    ////gaussianFilter.ApplyInPlace(colorImage.Bitmap);

        //    //RotateBilinearAndColor rotationFilter = new RotateBilinearAndColor(0.0, Color.White);
        //    //rotationFilter.FillColor = Color.FromArgb(0, 0, 0, 0);
        //    //Bitmap colorBrush = new Bitmap(scene.Painting.BackgroundImage.Bitmap);
        //    //Bitmap heightBrush = new Bitmap(scene.Painting.BackgroundImage.Bitmap);
        //    //Bitmap [] brushes;
        //    //BrushMipMap colorBrushMip = scene.Painting.Brush.ColorStraight;
        //    //BrushMipMap heightBrushMip = scene.Painting.Brush.Height;
        //    //gColor.CompositingMode = CompositingMode.SourceOver;
        //    //gColor.CompositingQuality = CompositingQuality.HighSpeed;

        //    //Random rand = new Random();
        //    ////int progressIncrementRate = numIter / 100;

        //    //anglesImage.Lock(ImageLockMode.ReadWrite);
        //    //colorImage.Lock(ImageLockMode.ReadOnly);
        //    //edgesImage.Lock(ImageLockMode.ReadOnly);

        //    //Pen pen = new Pen(new SolidBrush(Color.Black), lineWidth);

        //    //double angle;

        //    //for(int i = 0; i < numIter; i++)
        //    //{
        //    //    int xpos = rand.Next(width);
        //    //    int ypos = rand.Next(height);

        //    //    angle = ((double)anglesImage.GetCellBW(xpos, ypos)) / 255.0 * Math.PI * 2;

        //    //    // note it is blue, green, red, alpha in memory
        //    //    byte red = colorImage.GetCell(xpos, ypos, AForge.Imaging.RGBA.R);
        //    //    byte green = colorImage.GetCell(xpos, ypos, AForge.Imaging.RGBA.G);
        //    //    byte blue = colorImage.GetCell(xpos, ypos, AForge.Imaging.RGBA.B);
        //    //    byte alpha = colorImage.GetCell(xpos, ypos, AForge.Imaging.RGBA.A);
        //    //    //byte alpha = edgesImage.GetCellBW(xpos, ypos);

        //    //    rotationFilter.ToColor = Color.FromArgb(alpha, red, green, blue);
        //    //    rotationFilter.Angle = angle/Math.PI*180.0f;
        //    //    colorBrush = colorBrushMip.GetImage((int)lineLength).Bitmap;
        //    //    heightBrush = heightBrushMip.GetImage((int)lineLength).Bitmap;
        //    //    brushes = rotationFilter.Apply(colorBrush, heightBrush);
        //    //    colorBrush = brushes[0];
        //    //    heightBrush = brushes[1];

        //    //    float brushSize = lineLength * (1.0f - (edgesImage.GetCellBW(xpos, ypos) / 255.0f)) + 5.0f;

        //    //    xpos = xpos - colorBrush.Width/2;
        //    //    ypos = ypos - colorBrush.Height/2;

        //    //    gColor.DrawImage(colorBrush, xpos, ypos, brushSize, brushSize);
        //    //    gHeight.DrawImage(heightBrush, xpos, ypos, brushSize, brushSize);
        //    //    //g.DrawLine(pen, row, col, row + x, col + y);
        //    //    //g.DrawEllipse(pen, row, col, circleRadius, circleRadius);
        //    //    //if (progressIncrementRate != 0 && i % progressIncrementRate == 0)
        //    //    //    mToolStipProgressBar.Increment(1);
        //    //}

        //    //anglesImage.Unlock();
        //    //colorImage.Unlock();
        //    //edgesImage.Unlock();
        //    //gColor.Flush();

        //    //PBR.Filters.AlphaMatte alphaMatte = new PBR.Filters.AlphaMatte();
        //    //alphaMatte.ApplyInPlace(scene.Painting.ActiveLayer.ColorImage.Bitmap, 
        //    //    scene.Painting.ActiveLayer.PaintImage.Bitmap);

        //    ////mToolStipProgressBar.Value = 0;
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="currScene"></param>
        /// <param name="outputImage"></param>
        public void Execute()
        {
            BitmapUnsafe outputImage = new BitmapUnsafe(XMLSchema.OutputImage, scene.Painting.ActiveLayer.PaintImage);
            scene.Painting.OutputImage = outputImage;

            if (scene.Painting.Layers.Count == 0)
            {
                scene.ProgressBar.Value = scene.ProgressBar.Maximum;
                return;
            }
            Layer[] layers = scene.Painting.Layers.ToArray();

            // Progress bar reset
            scene.ProgressBar.Value = 0;
            int progressIncrementRate = (outputImage.Height * layers.Length) / scene.ProgressBar.Maximum;

            // Lock the data we are currently working on

            BitmapUnsafe interResults = new BitmapUnsafe("", outputImage);
            interResults.Lock(ImageLockMode.WriteOnly);
            outputImage.Lock(ImageLockMode.ReadWrite);

            int width = outputImage.Width;
            int height = outputImage.Height;
            float hScale = 2.0f / (width - 1);
            float vScaleDiv255;
            float diffuseCoef;
            float specularCoef;
            float ambientCoef;
            Color ambient;
            float shininess;
            Vector3 uVector;
            Vector3 vVector;
            Vector3 normal;
            Vector3 temp;
            Vector3 lPos = scene.Light.Pos;
            Vector3 lightVec;
            Vector3 viewVec;
            Vector3 reflectionVec;
            float diff1;
            float diff2;
            float xPos;
            float zPos;
            float fRed;
            float fGreen;
            float fBlue;
            float fRedNew;
            float fGreenNew;
            float fBlueNew;
            float fAlphaNew;
            float specMultiplier;
            float alpha;
            int currentLayer = 0;
            int interDivFactor = 2;

            foreach (Layer layer in scene.Painting.Layers)
            {
                // Get the material properties (b/c access is slow for some reason)
                //diffuseCoef = layer.Material.DiffuseCoef;
                //specularCoef = layer.Material.SpecularCoef;
                //ambientCoef = layer.Material.AmbientCoef;
                //ambient = layer.Material.Ambient;
                //shininess = layer.Material.Shininess;
                //vScaleDiv255 = layer.Material.VScale / 255.0f;

                // Lock the current layers data
                layer.PaintImage.Lock(ImageLockMode.ReadOnly);
                layer.SpecResultImage.Lock(ImageLockMode.ReadOnly);
                layer.HeightImage.Lock(ImageLockMode.ReadOnly);

                unsafe
                {
                    // Todo - remove either hScale or vScale -> prob hScale

                    // Compute the phong shading for each layer and composite
                    for (int col = 1; col < outputImage.Height - 1; col++)
                    {
                        for (int row = 1; row < outputImage.Width - 1; row++)
                        {
                            if (layer.PaintImage.GetCell(row, col, 3) == 0)
                            {
                                interResults.SetCell((byte)(outputImage.GetCell(row, col, 0) / interDivFactor), row, col, 0);
                                interResults.SetCell((byte)(outputImage.GetCell(row, col, 1) / interDivFactor), row, col, 1);
                                interResults.SetCell((byte)(outputImage.GetCell(row, col, 2) / interDivFactor), row, col, 2);
                                interResults.SetCell((byte)(outputImage.GetCell(row, col, 3) / interDivFactor), row, col, 3);
                                continue;
                            }

                            int segIndex = scene.Painting.ActiveLayer.ObjectLabels[row, col];
                            if (segIndex < 0)
                            {
                                continue;
                                //throw new Exception("SegIndex should not be zero");
                            }
                            Segment seg = (Segment)scene.Painting.ActiveLayer.segments[segIndex];
                            diffuseCoef = seg.Material.DiffuseCoef;
                            specularCoef = seg.Material.SpecularCoef;
                            ambientCoef = seg.Material.AmbientCoef;
                            ambient = seg.Material.Ambient;
                            shininess = seg.Material.Shininess;
                            vScaleDiv255 = seg.Material.VScale / 255.0f;

                            // Clear accum vector
                            normal = Vector3.FromXYZ(0.0f, 0.0f, 0.0f);

                            // upper left
                            diff1 = (float)(layer.HeightImage.GetCellAlpha(row, col + 1, 0) - layer.HeightImage.GetCellAlpha(row, col, 0)) * vScaleDiv255;
                            uVector = Vector3.FromXYZ(0.0f, diff1, 1.0f * hScale);
                            diff2 = (float)(layer.HeightImage.GetCellAlpha(row - 1, col, 0) - layer.HeightImage.GetCellAlpha(row, col, 0)) * vScaleDiv255;
                            vVector = Vector3.FromXYZ(-1.0f * hScale, diff2, 0.0f);
                            temp = Vector3.CrossProduct(vVector, uVector);
                            temp.Normalize();
                            normal += temp;

                            // upper right
                            diff1 = (float)(layer.HeightImage.GetCellAlpha(row + 1, col, 0) - layer.HeightImage.GetCellAlpha(row, col, 0)) * vScaleDiv255;
                            uVector = Vector3.FromXYZ(1.0f * hScale, diff1, 0.0f);
                            diff2 = (float)(layer.HeightImage.GetCellAlpha(row, col + 1, 0) - layer.HeightImage.GetCellAlpha(row, col, 0)) * vScaleDiv255;
                            vVector = Vector3.FromXYZ(0.0f, diff2, 1.0f * hScale);
                            temp = Vector3.CrossProduct(vVector, uVector);
                            temp.Normalize();
                            normal += temp;

                            // lower left
                            diff1 = (float)(layer.HeightImage.GetCellAlpha(row - 1, col, 0) - layer.HeightImage.GetCellAlpha(row, col, 0)) * vScaleDiv255;
                            uVector = Vector3.FromXYZ(-1.0f * hScale, diff1, 0.0f);
                            diff2 = (float)(layer.HeightImage.GetCellAlpha(row, col - 1, 0) - layer.HeightImage.GetCellAlpha(row, col, 0)) * vScaleDiv255;
                            vVector = Vector3.FromXYZ(0.0f, diff2, -1.0f * hScale);
                            temp = Vector3.CrossProduct(vVector, uVector);
                            temp.Normalize();
                            normal += temp;

                            // lower right
                            diff1 = (float)(layer.HeightImage.GetCellAlpha(row, col - 1, 0) - layer.HeightImage.GetCellAlpha(row, col, 0)) * vScaleDiv255;
                            uVector = Vector3.FromXYZ(0.0f, diff1, -1.0f * hScale);
                            diff2 = (float)(layer.HeightImage.GetCellAlpha(row + 1, col, 0) - layer.HeightImage.GetCellAlpha(row, col, 0)) * vScaleDiv255;
                            vVector = Vector3.FromXYZ(1.0f * hScale, diff2, 0.0f);
                            temp = Vector3.CrossProduct(vVector, uVector);
                            temp.Normalize();
                            normal += temp;

                            normal.Normalize();

                            xPos = (float)row / (width - 1) * 2.0f - 1.0f;
                            zPos = (float)col / (height - 1) * 2.0f - 1.0f;

                            lightVec = Vector3.FromXYZ(lPos[0] - xPos, lPos[1] - ((float)layer.HeightImage.GetCellAlpha(row, col, 0) * vScaleDiv255), lPos[2] - zPos);
                            lightVec.Normalize();

                            // Diffuse Component
                            fRed = diffuseCoef * Vector3.Dot(lightVec, normal) * layer.PaintImage.GetCell(row, col, 0);
                            fGreen = diffuseCoef * Vector3.Dot(lightVec, normal) * layer.PaintImage.GetCell(row, col, 1);
                            fBlue = diffuseCoef * Vector3.Dot(lightVec, normal) * layer.PaintImage.GetCell(row, col, 2);

                            // Specular Component
                            // Eye vector
                            viewVec = Vector3.FromXYZ(mEyePos.X - xPos, mEyePos.Y - layer.HeightImage.GetCellAlpha(row, col, 0) * vScaleDiv255, mEyePos.Z - zPos);
                            viewVec.Normalize();

                            // Reflection vector
                            reflectionVec = normal;
                            reflectionVec.Multiply(2 * Vector3.Dot(lightVec, normal));
                            reflectionVec.Subtract(lightVec);
                            reflectionVec.Normalize();

                            specMultiplier = (float)(Math.Pow(Vector3.DotProduct(reflectionVec, viewVec), shininess)) * specularCoef;
                            if (specMultiplier < 0.0f)
                                specMultiplier = 0.0f;
                            fRed += layer.SpecResultImage.GetCell(row, col, 0) * specMultiplier;
                            fGreen += layer.SpecResultImage.GetCell(row, col, 1) * specMultiplier;
                            fBlue += layer.SpecResultImage.GetCell(row, col, 2) * specMultiplier;
                            
                            // ambient
                            fRed += (seg.Material.AmbientCoef * seg.Material.Ambient.R * layer.PaintImage.GetCell(row, col, 0)) / 255.0f;
                            fGreen += (seg.Material.AmbientCoef * seg.Material.Ambient.G * layer.PaintImage.GetCell(row, col, 1)) / 255.0f;
                            fBlue += (seg.Material.AmbientCoef * seg.Material.Ambient.B * layer.PaintImage.GetCell(row, col, 2)) / 255.0f;

                            if (fRed > 255.0f)
                                fRed = 255.0f;
                            if (fGreen > 255.0)
                                fGreen = 255.0f;
                            if (fBlue > 255.0f)
                                fBlue = 255.0f;

                            interResults.SetCell((byte)fRed, row, col, 0);
                            interResults.SetCell((byte)fGreen, row, col, 1);
                            interResults.SetCell((byte)fBlue, row, col, 2);
                            interResults.SetCell(layer.PaintImage.GetCell(row, col, 3), row, col, 3);

                            if (layer.Equals(layers[0]))
                            {
                                alpha = (float)(layer.PaintImage.GetCell(row, col, 3)) / 255.0f;
                            }
                            else
                            {
                                alpha = (float)(layer.PaintImage.GetCell(row, col, 3) * layer.HeightImage.GetCell(row, col, 0)) / 65025.0f;
                            }
                            fRedNew = fRed * alpha + (1.0f - alpha) * outputImage.GetCell(row, col, 0);
                            fGreenNew = fGreen * alpha + (1.0f - alpha) * outputImage.GetCell(row, col, 1);
                            fBlueNew = fBlue * alpha + (1.0f - alpha) * outputImage.GetCell(row, col, 2);
                            fAlphaNew = (alpha * alpha* 255) + (1.0f - alpha) * outputImage.GetCell(row, col, 3);

                            outputImage.SetCell((byte)fRedNew, row, col, 0);
                            outputImage.SetCell((byte)fGreenNew, row, col, 1);
                            outputImage.SetCell((byte)fBlueNew, row, col, 2);
                            outputImage.SetCell((byte)fAlphaNew, row, col, 3);
                        }
                        if (col % progressIncrementRate == 0)
                            scene.ProgressBar.Increment(1);
                    }
                }
                // Unlock the current layers data
                layer.PaintImage.Unlock();
                layer.SpecResultImage.Unlock();
                layer.HeightImage.Unlock();

                // Save the intermediate results
                //string kSavePathLayer = Path.Combine(kBasePath, "result" + currentLayer.ToString() + ".png");
                //interResults.Bitmap.Save(kSavePathLayer);
                //outputImage.Bitmap.Save(kSavePathLayer);

                // Increment the layer counter
                currentLayer++;
            }

            // Unlock the intermediate results bitmap
            //interResults.Unlock();

            // Unlock the output image
            outputImage.Unlock();

            // Progress bar finish
            scene.ProgressBar.Value = scene.ProgressBar.Minimum;

            //AForge.Imaging.Filters.CannyEdgeDetector cannyEdgeDetector = new AForge.Imaging.Filters.CannyEdgeDetector();
            //cannyEdgeDetector.Apply(outputImage.Bitmap);

            return;        
        }

        #endregion

        #region Helper Functions

        private double ConvertToRadians(double degree)
        {
            return (Math.PI / 180.0) * degree;
        }
        #endregion


        #region Properties
        public Render.Painter Painter
        {
            get
            {
                return painter;
            }
            set
            {
                painter = value;
            }
        }

        #endregion
    }
}
